基于上篇博客对pyc结构的了解,继续看汇编指令。
Pyc字节码分析 2
汇编指令集
https://docs.python.org/3/library/dis.html 讲述py3的dis
模块,顺带了讲了一下指令含义。
python的指令集属于是代代更新的。每次都会多几个新指令,同时把一些不常用的旧指令又给抛弃掉。
先从python36开始,然后每个版本的删增都贴上来。
python36
1 | // python36 |
汇编指令可在Include/opcode.h
中找到。也可以在pycdc
主文件夹下的bytes
文件夹中找到一大堆.map
文件,这里有各个版本的指令。
python37
1 | 比对python36: |
python38
1 | diff python_37.map python_38.map |
python39
<
是删除,>
是新增
1 | diff python_38.map python_39.map |
python310
1 | diff python_39.map python_310.map |
这些指令是真的多。
有参指令
文章较老,格式是py27的,有参指令的规则似乎py3已经变化了。
1 | // Include/opcode.h |
即大于等于90的指令都是有参数的。
pyc文件 - CTF Wiki (ctf-wiki.org)
python3.6 以上参数永远占 1 字节(包括指令本身那就是一共占2个字节), 如果指令不带参数的话则以0x00
代替, 在运行过程中被解释器忽略, 也是 Stegosaurus
(一种.pyc
文件隐写工具)的技术原理;
而低于 python3.5 的版本中指令不带参数的话却没有0x00
填充。
老版本的python27则是一个指令占用1或3个字节。1就是无参数,3则是有参数,参数占2字节,且第二个一定是0x00。
举例
1 | # py3.7.9 |
Python虚拟机简介
原本打算自己写点的,但是网上查到了很多分析CPython
解释器的文章,所以也就不扯了。精简一下写一下重要的核心架构。
https://nanguage.gitbook.io/inside-python-vm-cn/1.-jian-jie
https://zhuanlan.zhihu.com/p/375323851
https://zhuanlan.zhihu.com/p/375323851
Python虚拟机详解_G1011的博客-CSDN博客_python虚拟机
《深度剖析CPython解释器》24. Python运行时环境的初始化、源码分析Python解释器在启动时都做了哪些事情? - 古明地盆 - 博客园 (cnblogs.com)
.pyc
文件是PyCodeObject
在磁盘上的表现形式,其中包含了字节码指令以及程序的所有静态信息- 执行时的动态环境则由
PyFrameObject
承担 PyInterpreterState
模拟进程PyThreadState
模拟线程- 位于
Python/ceval.c
的PyEval_EvalFrameEx()
函数是核心执行函数。
PyFrameObject
1 | // Include/frameobject.h |
每一个
PyFrameObject
对象都维护了一个PyCodeObject
对象,这表明每一个PyFrameObject
中的动态内存空间对象都和源代码中的一段代码相对应。
虚拟机简化结构
根据上面的栈帧结构,简化一下体系。
未经实际代码审计考证,但是能便于理解,且与实际相差不大。
Python虚拟机的核心就是一个栈,因为这是一个栈式虚拟机。实际上很多支持多架构的编程语言的虚拟机都是基于虚拟栈的,这样能够简化虚拟汇编指令的复杂度,也能方便移植。
同时我们也需要其他的一些东西,比如一些全局变量,或者一些必要的默认方法函数,比如上文所提f_globals
或f_builtins
都是字典对象,里面维护了函数和全局变量。
再有的就是PyCodeObject
里面自带的一些信息,比如**co_names
和co_consts
**。
当然还有自己的局部变量。**co_nlocals
显示局部变量(函数栈上的)的个数,co_varnames
**则存储局部变量名。详见上个博客。
与指令集的关系
举几个常见指令
LOAD_CONST
会将co_consts
中的元素加载进栈
LOAD_NAME
会将co_names
中的字符串进栈
STORE_NAME
会弹出栈顶元素,然后和co_names
顶部元素相对应。相当于一个赋值操作。
1 | LOAD_CONST "Hello" |
就相当于s = "Hello"
。
LOAD_FAST
就是加载局部变量进入栈中。
示例代码
举个例子
Py文件
基于上次的继续:
1 | # test.py |
Pyc文件
以及手动排版过的二进制代码:
1 | 42 0d 0d 0a // 版本魔数 |
下次可以考虑一下写个脚本打印了。
必要前置数据
待会汇编指令要用上的东西我们先整理出来。
文件名是test.cpython-37.pyc
,于是写个脚本把它读取出来。
1 | import marshal, dis |
得到一个名为b
的PyCodeObject
。
如上文所述,我们需要前置数据的是
co_names
co_consts
1 | b.co_consts |
反汇编co_code
代码
然后我们使用dis
模块反汇编出代码来:
1 | >>> dis.dis(b.co_code) |
LOAD_CONST 0
加载了co_consts
的第一个元素,即"Hello"
然后STORE_NAME 0
把栈顶元素"Hello"
弹出来存到了co_names
对应的第一个元素,也就是s
相当于s = "Hello"
后面的同理
这样我们就知道大致的意思了
1 | """ |
上面反汇编的是整个模块的逻辑。
然后再反汇编一下func
函数的逻辑
1 | # b.co_consts[2]就是func对象 |
1 | 2].co_code) dis.dis(b.co_consts[ |
补充
关于co_freevars
和co_cellvars
补充上次博客没仔细讲的点。这两个成员是用于Python闭包的。
1 | def outter(o1,o2): |
- 对于
outter
函数来说,他的局部变量fc1
和fc2
被它内部嵌套的函数所引用,则fc1
和fc2
变成它的cellvars
而不是局部变量varnames
- 对于
inner
函数,fc1
和fc2
既不是局部变量也不是全局变量,他引用自外层函数,则fc1
和fc2
是inner
的freevars
关于MAKE_FUNCTION汇编指令
上文示例用到了,但是没解释具体咋用。这里强调一下。
1 | // https://stackoverflow.com/questions/54877888/python-3-7-bytecode-make-function-how-to-interpret-such-disassembled-function-d |
关于其他指令
https://docs.python.org/zh-cn/3/library/dis.html
官方文档其实都有解释,且支持中文。
其他
逆向技巧
上一次尝试逆向python3.10的.pyc
文件时,我是对着题目的反汇编文本,写出猜出来的高级语言代码,然后再用dis
模块去反汇编它,得到的反汇编文本再和题目比对,一致就是猜对了。不完全一致,则稍微修改一下,反复尝试,直到一致。
待解决
关于pyinstaller打包时加密
随便乱翻文章的时候发现的问题。
https://z.itpub.net/article/detail/F9A280FCEFD651D8ED7E06DA707747F5
在结尾处,作者说能够传入--key
参数传入密码进行加密。
1 | pip install tinyaes |
1 | pyinstaller -Fw --icon=h.ico auto_organize_gui.py --add-data="h.ico;/" --key 123456 |
由于是本地加密,本地执行,所以按理文件会在执行的时候再次被解密,或者可能会在某个时刻泄露密钥。所以可以尝试逆向工程一番,或者通过审计源码,了解过程。
自动解析一般pyc文件结构的小脚本
上面是纯手撸的,有点蠢。有空写个自动化解析的。
pyc混淆
逆python–pyc文件结构及pyc混淆基础_招财猫的小叮当的博客-CSDN博客
想法挺好的,即网上大部分反汇编器都是线性扫描的,比如pycdas
,但是实际上Python解释器的执行是一个字节一个字节顺着执行流的,所以可以通过设计一些永不可达分支,在上面添加垃圾无效指令,从而实现反反汇编。
pyc汇编举例
像上次分析Cython一样,举一些常见例子来分析一下,总结一下大致结构。
但是由于这个难度比Cython简单很多很多,所以一篇博客稍微谈谈足矣。
pyc汇编指令大全
官网上有,但是没有具体例子。我准备基于官网上的简介再举一些例子,强化理解。
pyc高版本结构区别
python39和python310都是无法反编译的。主要是新增的指令;但在我的印象中,头文件似乎有一些区别。我稍微尝试一下吧。
参考
python 字节码死磕 - 大步向前blue - 博客园 (cnblogs.com)
https://nanguage.gitbook.io/inside-python-vm-cn/2.-the-view-from-30-000ft
https://z.itpub.net/article/detail/F9A280FCEFD651D8ED7E06DA707747F5
https://docs.python.org/zh-cn/3/library/dis.html
https://nanguage.gitbook.io/inside-python-vm-cn/1.-jian-jie
https://zhuanlan.zhihu.com/p/375323851
https://zhuanlan.zhihu.com/p/375323851
Python虚拟机详解_G1011的博客-CSDN博客_python虚拟机
《深度剖析CPython解释器》24. Python运行时环境的初始化、源码分析Python解释器在启动时都做了哪些事情? - 古明地盆 - 博客园 (cnblogs.com)
- 本文作者: Taardis
- 本文链接: https://taardisaa.github.io/2022/02/11/Pyc Reverse 2/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!